/**
 * Portions copyright (C) 2000, 2001 Maynard Demmon, maynard@organic.com
 * Portions copyright (C) 2002  Stan Krute <Stan@StanKrute.com>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or 
 * without modification, are permitted provided that the 
 * following conditions are met:
 * 
 *  - Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer. 
 * 
 *  - Redistributions in binary form must reproduce the above 
 *    copyright notice, this list of conditions and the following 
 *    disclaimer in the documentation and/or other materials provided 
 *    with the distribution. 
 * 
 *  - Neither the names "Java Outline Editor", "JOE" nor the names of its 
 *    contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 */
 
package com.organic.maynard.outliner;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.Transferable;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.XMLDecoder;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Properties;
import java.util.logging.Logger;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.WindowConstants;

import org.xml.sax.Attributes;

import com.organic.maynard.outliner.dom.DocumentRepository;
import com.organic.maynard.outliner.guitree.GUITreeComponent;
import com.organic.maynard.outliner.guitree.GUITreeComponentRegistry;
import com.organic.maynard.outliner.guitree.GUITreeLoader;
import com.organic.maynard.outliner.io.FileFormatManager;
import com.organic.maynard.outliner.io.FileProtocol;
import com.organic.maynard.outliner.io.FileProtocolManager;
import com.organic.maynard.outliner.io.LoadFileFormatClassCommand;
import com.organic.maynard.outliner.io.LoadFileProtocolClassCommand;
import com.organic.maynard.outliner.menus.OutlinerDesktopMenuBar;
import com.organic.maynard.outliner.menus.file.FileMenu;
import com.organic.maynard.outliner.menus.file.QuitMenuItem;
import com.organic.maynard.outliner.menus.window.WindowMenu;
import com.organic.maynard.outliner.model.DocumentInfo;
import com.organic.maynard.outliner.model.OutlinerModel;
import com.organic.maynard.outliner.util.preferences.Preferences;
import com.organic.maynard.outliner.util.preferences.SetPrefCommand;
import com.organic.maynard.util.Command;
import com.organic.maynard.util.CommandParser;
import com.organic.maynard.util.UnknownCommandException;

/**
 * @author  $Author: maynardd $
 * @version $Revision: 1.89 $, $Date: 2004/03/22 04:48:03 $
 */

public class Outliner extends JFrame implements ClipboardOwner, GUITreeComponent, JoeXMLConstants {
	
	// Constants
	public static final boolean DEBUG = false;
	private static final Logger logger = Logger.getLogger("outliner");
	
	// Language Handling
	public static String LANGUAGE = "en"; // Defaults to English.
	
	// Preferences object
	private OutlinerModel model = new OutlinerModel();
	private Properties preferences = new Properties();
			
	// Directory setup
	public static final String FILE_SEPARATOR = System.getProperty("file.separator");
	
	public static final String USER_OUTLINER_DIR = ".tinyoutliner";
	
	// [deric] 31sep2001, We want to be able to specify the graphics dir via a Property in the packaging for MacOS X. If it isn't defined it defaults to the usual "graphics". 
	public static String PREFS_DIR = new StringBuffer().append(System.getProperty("com.organic.maynard.outliner.Outliner.prefsdir", "prefs")).append(FILE_SEPARATOR).toString();
	public static String USER_PREFS_DIR = PREFS_DIR;
	public static final String APP_DIR_PATH = new StringBuffer().append(System.getProperty("user.dir")).append(FILE_SEPARATOR).toString();
	
	
	// Find out if we've got a home directory to work with for user preferences, if
	// not then we use the prefs dir as usual.
	static {
		String userhome = System.getProperty("user.home");
		logger.info("User Home: " + userhome);
		if ((userhome != null) && !userhome.equals("")) {
			USER_PREFS_DIR = new StringBuffer().append(userhome).append(FILE_SEPARATOR).append(USER_OUTLINER_DIR).append(FILE_SEPARATOR).toString();
			PREFS_DIR= USER_PREFS_DIR;
		}
	}
	
	// These prefs should be under the users prefs dir, or if no user prefs dir exists then
	// they should be under the apps prefs dir.
	public static String CONFIG_FILE =       new StringBuffer().append(USER_PREFS_DIR).append("config.txt").toString();
	public static String RECENT_FILES_FILE = new StringBuffer().append(USER_PREFS_DIR).append("recent_files.xml").toString();
	public static String OPEN_FILES_FILE =   new StringBuffer().append(USER_PREFS_DIR).append("open_files.xml").toString();
	public static String FILE_FORMATS_FILE =   new StringBuffer().append(PREFS_DIR).append("file_formats.txt").toString();
	private static String PREFERENCES_FILE = new StringBuilder(USER_PREFS_DIR).append("preferences.properties").toString();
	
	
	// This static block should be considered "installer" functionality. 
	// At some point this could all be moved to an installer app.
	// Make the directories in case they don't exist.
	static {
		boolean isCreated = false;
		
		File prefsFile = new File(PREFS_DIR);
		isCreated = prefsFile.mkdirs();
		if (isCreated) {
			System.out.println("Created Preferences Directory: " + prefsFile.getPath());
		}
		
		File userPrefsFile = new File(USER_PREFS_DIR);
		isCreated = userPrefsFile.mkdirs();
		if (isCreated) {
			System.out.println("Created User Preferences Directory: " + userPrefsFile.getPath());
		}
		
		File formats = new File(FILE_FORMATS_FILE);
		if(!formats.exists()) {
			createDefaultConfigFile(formats,"/resources/file_formats.txt");
		}
		
		File preferences = new File(PREFERENCES_FILE);
		if(!preferences.exists()) {
			try {
				preferences.createNewFile();
			} catch (IOException e) {
				logger.warning("Could not create the preferences file");
			}
		}
}


	private static void createDefaultConfigFile(File formats, String resourceString) {
		try {
			PrintWriter pw = new PrintWriter( new BufferedWriter( new FileWriter(formats)));
			InputStream is = Outliner.class.getResourceAsStream(resourceString);
			
			BufferedReader br = new BufferedReader(new InputStreamReader(is));
			String line = null;
			while((line = br.readLine()) != null) {
				pw.println(line);
			}
			pw.flush();
			pw.close();
			br.close();
			is.close();
		} catch (IOException ioe) {
			logger.severe("Could not create preference file for resource: " + resourceString);
		}
	}
	
	// XML Parser
	//public static final Parser XML_PARSER = new com.jclark.xml.sax.Driver();
	public static final GUITreeLoader GUI_TREE_LOADER = new GUITreeLoader();
	
	
	// Command Parser
	public static final String COMMAND_PARSER_SEPARATOR = "|";
	public static final String COMMAND_SET = "set";
	public static final String COMMAND_MACRO_CLASS = "macro_class";
	public static final String COMMAND_MACRO = "macro";
	public static final String COMMAND_SCRIPT_CLASS = "script_class";
	public static final String COMMAND_SCRIPT = "script";
	public static final String COMMAND_FILE_FORMAT = "file_format";
	public static final String COMMAND_FILE_PROTOCOL = "file_protocol";
	
	public static final Command SET_PREF_COMMAND =                 new SetPrefCommand(COMMAND_SET);
	public static final Command LOAD_FILE_FORMAT_CLASS_COMMAND =   new LoadFileFormatClassCommand(COMMAND_FILE_FORMAT);
	public static final Command LOAD_FILE_PROTOCOL_CLASS_COMMAND = new LoadFileProtocolClassCommand(COMMAND_FILE_PROTOCOL);
	
	public static final CommandParser PARSER = new CommandParser(COMMAND_PARSER_SEPARATOR);
	
	static {
		PARSER.addCommand(SET_PREF_COMMAND);
		PARSER.addCommand(LOAD_FILE_FORMAT_CLASS_COMMAND);
		PARSER.addCommand(LOAD_FILE_PROTOCOL_CLASS_COMMAND);
	}
	
	
	// GUI Objects
	public static FileFormatManager fileFormatManager = null;
	public static FileProtocolManager fileProtocolManager = null;
	private static Preferences prefs = null;
	// DOM Objects
	public static DocumentRepository documents = new DocumentRepository();
	
	// GUI Settings
	public static Outliner outliner = null;
	public static OutlinerDesktop desktop = new OutlinerDesktop();
	public static JScrollPane jsp = null;
	public static OutlinerDesktopMenuBar menuBar = null;
	public static DocumentStatistics statistics = null;
	public static DocumentAttributesView documentAttributes = null;
	
	
	private static final String guiFileResourceName = new StringBuffer("/resources/").append("gui_tree.")
		.append(Locale.getDefault().getLanguage()).append(".xml").toString();
	
	public static URL getGuiXmlFile() {
		//return Thread.currentThread().getContextClassLoader().getResource("com.organic.maynard.outliner.gui_tree.xml");
		//return Outliner.class.getResource("com.organic.maynard.outliner.gui_tree.xml");
		return Outliner.class.getResource(guiFileResourceName);
	}
	
	
	// Main
	public static void main(String args[]) {
		// Property assignment to enable the top-level menubar in Mac OS X (does nothing on other platforms)
		System.setProperty("apple.laf.useScreenMenuBar","true");
		
		// Set the preferred language to use.
		String ISO639LangCode = Locale.getDefault().getLanguage();
		if (ISO639LangCode != null && !ISO639LangCode.equals("")) {
			// Verify that gui_tree.xml file exists for this language.
			File file = new File(new StringBuffer().append(PREFS_DIR).append("gui_tree.").append(ISO639LangCode).append(".xml").toString());
			if (file.exists()) {
				LANGUAGE = ISO639LangCode;
			} else {
				System.out.println("WARNING: Default language code, " + ISO639LangCode + " does not have a gui_tree.xml file. Using default value instead.");
			}
		}
		
		// Load the Main GUITree
		boolean guiDefinitionLoaded = false;
		try {
			String guiXmlFile = new StringBuilder("gui_tree.").append(LANGUAGE).append(".xml").toString();
			InputStream guiFileStream = Outliner.class.getResourceAsStream("/resources/" + guiXmlFile);
			if (guiFileStream != null) {
				logger.info("Input Stream established");
				guiDefinitionLoaded = GUI_TREE_LOADER.load(guiFileStream);
			} else {
				// Fall back to loading from file
				String guiTreeFileName = new StringBuilder().append(PREFS_DIR).append(guiXmlFile).toString();
				guiDefinitionLoaded = GUI_TREE_LOADER.load(guiTreeFileName);
			}
		} finally {
			if (!guiDefinitionLoaded) {
				System.out.println("GUI Loading Error: exiting.");
				return;
			}
		}
		

		
		// Open documents from open documents list.
		//if (Preferences.getPreferenceBoolean(Preferences.OPEN_DOCS_ON_STARTUP).cur) {
		if(Boolean.valueOf(outliner.getPreferences().getProperty("startup.open-documents", "true"))) {
			//java.util.List<DocumentInfo> openDocumentsList = PropertyContainerUtil.parseXML(Outliner.OPEN_FILES_FILE);
			java.util.List<DocumentInfo> openDocumentsList = new ArrayList<DocumentInfo>();
			try {
				File openFilesFile = new File(Outliner.OPEN_FILES_FILE);
				if(openFilesFile.exists()) {
					XMLDecoder d = new XMLDecoder(new FileInputStream(openFilesFile));
					openDocumentsList = (java.util.List<DocumentInfo>) d.readObject();
				}
			} catch (IOException ioe) {
				logger.warning("Could not read the Open Files List successfully: " + ioe.getMessage());
			}
			for (int i = 0; i < openDocumentsList.size(); i++) {
				DocumentInfo docInfo = (DocumentInfo) openDocumentsList.get(i);
				FileProtocol fileProtocol = fileProtocolManager.getProtocol(docInfo.getProtocolName());
				assert fileProtocol != null: "Could not lookup a protocol";
				FileMenu.openFile(docInfo, fileProtocol);
			}
		}
		
		// See if the command line included a file to be opened.
		if (args.length > 0) {      // srk - put this test in -- 2002.09.03
			StringBuffer sb_filepath = new StringBuffer(args[0]);
			for (int i = 1; i < args.length; i++) {
				sb_filepath.append(" ").append(args[i]);
			}
			String filepath = sb_filepath.toString();
			if ((filepath != null) && (!filepath.equals("")) && (!filepath.equals("%1")) ) {
				try {
					// ensure that we have a full pathname [srk]
					filepath = new File(filepath).getCanonicalPath();
					
					// grab the file's extension
					String extension = filepath.substring(filepath.lastIndexOf(".") + 1, filepath.length());
					
					// use the extension to figure out the file's format
					String fileFormat = Outliner.fileFormatManager.getOpenFileFormatNameForExtension(extension);
					
					// crank up a fresh docInfo struct
					DocumentInfo docInfo = new DocumentInfo();
					docInfo.setFilePath(filepath);
					docInfo.setFileFormat(Outliner.fileFormatManager.getOpenFormat(fileFormat).getName());
					//PropertyContainerUtil.setPropertyAsString(docInfo, DocumentInfo.KEY_FILE_FORMAT, fileFormat);
					
					// try to open up the file
					FileMenu.openFile(docInfo, fileProtocolManager.getDefault());
				} catch (IOException e) {
					System.out.println("IOException: " + e.getMessage());
				}
			}
		} else {
			// Create a Document. This must come after visiblity otherwise the window won't be activated.
			//if (Preferences.getPreferenceBoolean(Preferences.NEW_DOC_ON_STARTUP).cur) {
			if (Boolean.valueOf(outliner.getPreferences().getProperty("startup.new-document", "false"))) {
				OutlinerDocument doc = new OutlinerDocument("");
				WindowMenu.changeToWindow(doc);
			}
		}
		
	}
	
	
	// GUITreeComponent interface
	private String id = null;
	public String getGUITreeComponentID() {return this.id;}
	public void setGUITreeComponentID(String id) {this.id = id;}
	
	public void startSetup(Attributes atts) {
		outliner = this;
		
		setTitle(GUITreeLoader.reg.getText("application.title"));
		
		// Load Preferences
		//loadPrefsFile(PARSER, ENCODINGS_FILE);
		
		// Setup the FileFormatManager and FileProtocolManager
		fileFormatManager = new FileFormatManager();
		fileProtocolManager = new FileProtocolManager();
		
		loadPrefsFile(PARSER, FILE_FORMATS_FILE);
		try {
			outliner.getPreferences().load(new FileInputStream(PREFERENCES_FILE));
		} catch (IOException ioe) {
			logger.severe("Could not load preferences: " + ioe.getMessage());
			System.exit(1);
		}
	}
	
	public void endSetup(Attributes atts) {
		// Set the Window Location.
		int initialWidth = getModel().getMainWindowWidth();
		int initialHeight = getModel().getMainWindowHeight();
		int initialPositionX = getModel().getMainWindowXPosition();
		int initialPositionY = getModel().getMainWindowYPosition();
			
		// Make sure initial position isn't off screen, or even really close to the edge.
		int bottom_left_inset = 100;
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
			
		if (initialPositionX > (screenSize.width - bottom_left_inset)) {
			initialPositionX = screenSize.width - bottom_left_inset;
		}
			
		if (initialPositionY > (screenSize.height - bottom_left_inset)) {
			initialPositionY = screenSize.height - bottom_left_inset;
		}
			
		setLocation(initialPositionX, initialPositionY);
			
		addComponentListener(new WindowSizeManager(initialWidth, initialHeight, 100, 100));
		final QuitMenuItem qmi = (QuitMenuItem) GUITreeLoader.reg.get(GUITreeComponentRegistry.QUIT_MENU_ITEM);
		qmi.setOutliner(this);
		addWindowListener(
			new WindowAdapter() {
				public void windowClosing(WindowEvent e) {
					getModel().savePreferences(Outliner.outliner);
					qmi.quit();
				}
			}
		);
		
		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		
		// Setup Desktop ScrollPane and set the ContentPane.
		jsp = new JScrollPane(desktop, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		jsp.addComponentListener(new DesktopScrollPaneComponentListener());
		setContentPane(jsp);
		
		// Set Frame Icon
		ImageIcon icon = new ImageIcon(Thread.currentThread().getContextClassLoader().getResource("graphics/frame_icon.gif"));
		setIconImage(icon.getImage());
		
		// Initialize open/save_as/export/export_selection menus.
		fileProtocolManager.synchronizeMenus();
							
		// Apply the Preference Settings
		Preferences.applyCurrentToApplication();
		
		setVisible(true); // Seems OK to do this now rather than at the end of this method.
		
		// Generate Icons
		OutlineButton.createIcons();
	}
	
	
	// ClipboardOwner Interface
	public static Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
	
	public void lostOwnership(Clipboard clipboard, Transferable contents) {}
	
	
	// Misc Methods
	public static void loadPrefsFile(CommandParser parser, String filename) {
		try {
			BufferedReader buffer = new BufferedReader(new InputStreamReader(new FileInputStream(filename)));
			
			String line = buffer.readLine();
			while (line != null) {
				try {
					parser.parse(line, true, true);
				} catch (UnknownCommandException uce) {
					System.out.println("Unknown Command");
				}
				
				line = buffer.readLine();
			}
			
			buffer.close();
		} catch (FileNotFoundException fnfe) {
			System.err.println("File Not Found: " + filename + "\n" + fnfe);
		} catch (Exception e) {
			System.err.println("Could not create FileReader: " + filename + "\n" + e);
		}
	}
	
	public static void setPrefs(Preferences prefs) {
		Outliner.prefs = prefs;
	}
	public static Preferences getPrefs() {
		return prefs;
	}
	
	// all calls for a new JoeTree come thru here
	// if changing the class that's implementing JoeTree, do so here
	public static JoeTree newTree (OutlinerDocument document) {
		// JoeTree currently implemented by TreeContext
		if (document == null) {
			return new TreeContext();
		} else {
			return new TreeContext(document);
		}
	}
	
	
	// all calls for a new JoeNodeList come thru here
	// if changing the class that's implementing JoeNodeList, do so here
	public static JoeNodeList newNodeList (int initialCapacity) {
		// JoeNodeList currently implemented by NodeList
		return new NodeList(initialCapacity);
	}
	
	public OutlinerModel getModel() { return model; }
	
	public Properties getPreferences() { return preferences; }
	public FileFormatManager getFileFormatManager() { return fileFormatManager; }


	public static boolean hasPreferences() {
		if(Outliner.getPrefs() != null) {
			return true;
		}
		return false;
	}
} // End of the Outliner class
